pub use config::SqlConfig;
use serde::Deserialize;
use std::{
    collections::BTreeMap,
    io::{Seek, Write},
};
use thiserror::Error;
use to_excel::Excel;
use to_html::Html;
use to_markdown::Markdown;
use to_word::{write, Word};

mod config;
mod mssql;
mod mysql;
mod to_excel;
mod to_html;
mod to_markdown;
mod to_word;

#[derive(sqlx::FromRow, Debug, Deserialize)]
#[sqlx(rename_all = "SCREAMING_SNAKE_CASE")]
struct Info {
    ordinal_position: u64,
    table_name: String,
    data_type: String,
    is_nullable: String,
    column_name: String,
    column_comment: Option<String>,
}

#[derive(Error, Debug)]
pub enum RunError {
    #[error("执行sql查询时出错")]
    Sqlx(#[from] sqlx::Error),
    #[error("执行sql查询时出错")]
    MsSql(#[from] tiberius::error::Error),
    #[error("生成word文档时出错")]
    Doc(#[from] docx_rs::DocxError),
}

trait ExportAction {
    fn run(config: &SqlConfig, groups: &BTreeMap<&String, Vec<&Info>>) -> Result<String, RunError>;
}
pub enum SqlType {
    Mysql,
    Mssql,
}

pub enum ExportType {
    Docx,
    Markdown,
    Html,
    Excel,
}

/// 根据不同的SQL类型，异步执行数据库查询并生成结果文件。
///
/// 参数:
/// - `sql_type`：查询使用的SQL类型，支持MySQL和SQL Server。
/// - `config`：数据库配置信息，用于连接和查询数据库。
///
/// 返回值:
/// - 成功时返回输出文件的路径作为字符串。
/// - 失败时返回一个包含错误信息的`RunError`。
pub async fn run(
    sql_type: SqlType,
    config: &SqlConfig,
    export: ExportType,
) -> Result<String, RunError> {
    let row = match sql_type {
        SqlType::Mysql => mysql::fetch_data(config).await?,
        SqlType::Mssql => mssql::fetch_data(config).await?,
    };
    let row = row.iter().collect::<Vec<_>>();
    let groups = group_table(&row);
    match export {
        ExportType::Docx => Word::run(config, &groups),
        ExportType::Markdown => Markdown::run(config, &groups),
        ExportType::Html => Html::run(config, &groups),
        ExportType::Excel => Excel::run(config, &groups),
    }
}

/// 异步执行数据库写入任务。
///
/// # 参数
/// - `sql_type`: 数据库类型，决定使用哪种数据库协议来执行数据抓取。
/// - `config`: 一个引用，指向包含数据库连接配置信息的结构体。
/// - `writer`: 一个可变引用，指向实现了`Write`和`Seek`特性的写入目标，用于输出处理后的数据。
///
/// ## 泛型约束
/// - `T`: 必须实现`Write`和`Seek`特质。
///
/// # 返回值
/// - `Result<(), RunError>`：操作成功返回`Ok(())`，失败则返回具体的错误信息。
///
/// # 注意
/// 目前仅支持MySQL和MSSQL两种数据库类型。
pub async fn run_writer<T>(
    sql_type: SqlType,
    config: &SqlConfig,
    writer: &mut T,
) -> Result<(), RunError>
where
    T: Write + Seek,
{
    let row = match sql_type {
        SqlType::Mysql => mysql::fetch_data(config).await?,
        SqlType::Mssql => mssql::fetch_data(config).await?,
    };
    let row_vec = row.iter().collect::<Vec<_>>();
    let grouped_data = group_table(&row_vec);
    write(&grouped_data, writer)?;
    Ok(())
}

/// 按表名分组
fn group_table<'a>(row: &'a [&'a Info]) -> BTreeMap<&'a String, Vec<&'a Info>> {
    row.iter().fold(BTreeMap::new(), |mut map, cur| {
        let key = &cur.table_name;
        let entry = map.entry(key).or_default();
        entry.push(cur);
        map
    })
}
